
我们已经实现了指令选择类和一些其他类。现在，需要设置后端如何工作。与优化流水线一样，后端也分为几个通道。配置这些通道是M88kTargetMachine类的主要任务，所以需要指定哪些特性可用于指令选择。通常，平台是一系列CPU，它们都有一组通用的指令，但因特定的扩展而有所不同。例如，一些CPU有向量指令，而另一些没有。在LLVM IR中，函数可以附加属性，这些属性指定应该为哪个CPU编译该函数，或者可以使用哪些特性。换句话说，每个函数可以有不同的配置，这是在M88kSubTarget类中捕获的。

\mySubsubsection{12.6.1.}{实现M88kSubtarget}

首先实现M88kSubtarget类，声明存储在M88kSubtarget.h类中:

\begin{enumerate}
\item
子目标的部分代码由目标描述生成，首先包含这些代码:

\begin{cpp}
#define GET_SUBTARGETINFO_HEADER
#include "M88kGenSubtargetInfo.inc"
\end{cpp}

\item
然后，声明这个类，从生成的M88kGenSubtargetInfo类派生。类拥有两个先前定义的类——指令信息、目标向下转译类和帧向下转译类:

\begin{cpp}
namespace llvm {
class StringRef;
class TargetMachine;
class M88kSubtarget : public M88kGenSubtargetInfo {
    virtual void anchor();

    Triple TargetTriple;
    M88kInstrInfo InstrInfo;
    M88kTargetLowering TLInfo;
    M88kFrameLowering FrameLowering;
\end{cpp}

\item
子目标是用目标三元组、CPU名称和一个特征字符串，以及目标机器初始化的。所有这些参数描述了后端为硬件生成的代码:

\begin{cpp}
public:
    M88kSubtarget(const Triple &TT,
                  const std::string &CPU,
                  const std::string &FS,
                  const TargetMachine &TM);
\end{cpp}

\item
接下来，会再次包含生成的文件，这次是为了为目标描述中定义的特性自动定义getter方法:

\begin{cpp}
#define GET_SUBTARGETINFO_MACRO(ATTRIBUTE, DEFAULT, \
                                GETTER) \
    bool GETTER() const { return ATTRIBUTE; }
#include "M88kGenSubtargetInfo.inc"
\end{cpp}

\item
此外，需要声明ParseSubtargetFeatures()方法。方法本身是从目标描述生成的:

\begin{cpp}
    void ParseSubtargetFeatures(StringRef CPU,
                                StringRef TuneCPU,
                                StringRef FS);
\end{cpp}

\item
接下来，必须为成员变量添加getter方法:

\begin{cpp}
    const TargetFrameLowering *
    getFrameLowering() const override {
        return &FrameLowering;
    }
    const M88kInstrInfo *getInstrInfo() const override {
        return &InstrInfo;
    }
    const M88kTargetLowering *
    getTargetLowering() const override {
        return &TLInfo;
    }
\end{cpp}

\item
最后，必须为寄存器信息添加getter方法，该方法属于指令信息类。这就结束了声明:

\begin{cpp}
    const M88kRegisterInfo *
    getRegisterInfo() const override {
        return &InstrInfo.getRegisterInfo();
    }
};
} // end namespace llvm
\end{cpp}

\end{enumerate}

接下来，必须实现实际的子目标类。实现存储在M88kSubtarget.cpp文件中:

\begin{enumerate}
\item
同样，通过包含生成的源代码来开始文件:

\begin{cpp}
#define GET_SUBTARGETINFO_TARGET_DESC
#define GET_SUBTARGETINFO_CTOR
#include "M88kGenSubtargetInfo.inc"
\end{cpp}

\item
然后，定义anchor方法，将虚函数表固定在这个文件上:

\begin{cpp}
void M88kSubtarget::anchor() {}
\end{cpp}

\item
最后，定义构造函数，生成的类需要两个CPU参数:第一个用于指令集，第二个用于调度。这里的用例是，希望针对最新的CPU优化代码，但仍然能够在较旧的CPU上运行代码。我们不支持这个特性，两个参数使用相同的CPU名称:

\begin{cpp}
M88kSubtarget::M88kSubtarget(const Triple &TT,
                             const std::string &CPU,
                             const std::string &FS,
                             const TargetMachine &TM)
    : M88kGenSubtargetInfo(TT, CPU, /*TuneCPU*/ CPU, FS),
      TargetTriple(TT), InstrInfo(*this),
      TLInfo(TM, *this), FrameLowering() {}
\end{cpp}
\end{enumerate}

\mySubsubsection{12.6.2.}{实现M88kTargetMachine——定义}

最后，可以实现M88kTargetMachine类。该类保存所有使用的子目标实例，还拥有TargetLoweringObjectFile的子类，该子类为向下转译过程提供了诸如节名之类的详细信息。最后，创建在此后端中运行通道的配置。

M88kTargetMachine.h文件中的声明如下:

\begin{enumerate}
\item
M88kTargetMachine类派生自LLVMTargetMachine类，唯一的成员是TargetLoweringObjectFile的一个实例和子目标映射:

\begin{cpp}
namespace llvm {
class M88kTargetMachine : public LLVMTargetMachine {
    std::unique_ptr<TargetLoweringObjectFile> TLOF;
    mutable StringMap<std::unique_ptr<M88kSubtarget>>
        SubtargetMap;
\end{cpp}

\item
构造函数的参数完全描述了，将为其生成代码的目标配置。使用TargetOptions类，可以控制代码生成的许多细节——例如，是否可以使用浮点乘法和加法指令。此外，重定位模型、代码模型和优化级别可传递给构造函数。若目标机器用于实时编译，则JIT参数设置为true。

\begin{cpp}
public:
    M88kTargetMachine(const Target &T, const Triple &TT,
                      StringRef CPU, StringRef FS,
                      const TargetOptions &Options,
                      std::optional<Reloc::Model> RM,
                      std::optional<CodeModel::Model> CM,
                      CodeGenOpt::Level OL, bool JIT);
\end{cpp}

\item
我们还需要重写一些方法。getSubtargetImpl()方法返回要用于给定函数的子目标实例，而getObjFileLowering()方法只返回成员变量。另外，覆写了createPassConfig()方法，该方法返回后端传递的配置:

\begin{cpp}
    ~M88kTargetMachine() override;

    const M88kSubtarget *
    getSubtargetImpl(const Function &) const override;

    TargetPassConfig *
    createPassConfig(PassManagerBase &PM) override;

    TargetLoweringObjectFile *
    getObjFileLowering() const override {
        return TLOF.get();
    }
};
} // end namespace llvm
\end{cpp}

\end{enumerate}

\mySubsubsection{12.6.3.}{实现M88kTargetMachine——添加实现}

该类的实现存储在M88kTargetMachine.cpp文件中。注意，在第11章中创建了这个文件。现在，我们将用一个完整的实现来替换这个文件:

\begin{enumerate}
\item
首先，必须注册目标机器，必须通过前面定义的初始化函数初始化DAG到DAG的通道:

\begin{cpp}
extern "C" LLVM_EXTERNAL_VISIBILITY void
LLVMInitializeM88kTarget() {
    RegisterTargetMachine<M88kTargetMachine> X(
        getTheM88kTarget());
    auto &PR = *PassRegistry::getPassRegistry();
    initializeM88kDAGToDAGISelPass(PR);
}
\end{cpp}

\item
接下来，必须定义支持函数computeDataLayout()。这个函数中，数据布局作为后端，期望定义。由于数据布局取决于硬件特性，因此三元组、CPU名称和特性集字符串将传递给该函数，使用以下组件创建数据布局字符串。目标是大端(E)，并使用ELF符号识别。

指针是32位宽且32位对齐的，所有标量类型都是自然对齐的。MC88110 CPU具有扩展寄存器集，并支持80位宽的浮点数。若要支持这个特殊的特性，则需要添加对CPU名称的检查，并用相应的浮点值扩展字符串。接下来，必须声明所有全局变量都有16位的首选对齐方式，并且硬件只有32位寄存器:

\begin{cpp}
namespace {
std::string computeDataLayout(const Triple &TT,
                              StringRef CPU,
                              StringRef FS) {
    std::string Ret;
    Ret += "E";
    Ret += DataLayout::getManglingComponent(TT);
    Ret += "-p:32:32:32";
    Ret += "-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64";
    Ret += "-f32:32:32-f64:64:64";
    Ret += "-a:8:16";
    Ret += "-n32";
    return Ret;
}
} // namespace
\end{cpp}

\item
现在，可以定义构造函数和析构函数，许多参数只是传递给超类构造函数。注意，这里调用了computeDataLayout()函数。此外，TLOF成员使用TargetLoweringObjectFileELF实例初始化，所以使用的是ELF文件格式。在构造函数体中，必须调用initAsmInfo()方法，该方法初始化超类的许多数据成员:

\begin{cpp}
M88kTargetMachine::M88kTargetMachine(
    const Target &T, const Triple &TT, StringRef CPU,
    StringRef FS, const TargetOptions &Options,
    std::optional<Reloc::Model> RM,
    std::optional<CodeModel::Model> CM,
    CodeGenOpt::Level OL, bool JIT)
    : LLVMTargetMachine(
        T, computeDataLayout(TT, CPU, FS), TT, CPU,
        FS, Options, !RM ? Reloc::Static : *RM,
        getEffectiveCodeModel(CM, CodeModel::Medium),
        OL),
    TLOF(std::make_unique<
        TargetLoweringObjectFileELF>()) {
    initAsmInfo();
}

M88kTargetMachine::~M88kTargetMachine() {}
\end{cpp}

\item
之后，定义getSubtargetImpl()方法。要使用的子目标实例取决于target-cpu和target-features函数属性。例如，可以将target-cpu属性设置为MC88110，从而针对第二代CPU。属性目标特性可以描述，不应该使用该CPU的图形指令。但还没有在目标描述中定义CPU和它们的特性，所以在这里做了一些不必要的事情，但实现非常简单:查询函数属性并使用返回的字符串或默认值。有了这些信息，可以查询SubtargetMap成员，若没有找到，则创建子目标:

\begin{cpp}
const M88kSubtarget *
M88kTargetMachine::getSubtargetImpl(
        const Function &F) const {
    Attribute CPUAttr = F.getFnAttribute("target-cpu");
    Attribute FSAttr =
        F.getFnAttribute("target-features");

    std::string CPU =
        !CPUAttr.hasAttribute(Attribute::None)
            ? CPUAttr.getValueAsString().str()
            : TargetCPU;
    std::string FS = !FSAttr.hasAttribute(Attribute::None)
                        ? FSAttr.getValueAsString().str()
                        : TargetFS;

    auto &I = SubtargetMap[CPU + FS];
    if (!I) {
        resetTargetOptions(F);
        I = std::make_unique<M88kSubtarget>(TargetTriple,
                                            CPU, FS, *this);
    }
    return I.get();
}
\end{cpp}

\item
最后，创建通道配置，需要自己的类M88kPassConfig，其派生自TargetPassConfig类，只需要重写addInstSelector方法即可:

\begin{cpp}
namespace {
class M88kPassConfig : public TargetPassConfig {
    public:
    M88kPassConfig(M88kTargetMachine &TM,
                   PassManagerBase &PM)
        : TargetPassConfig(TM, PM) {}
    bool addInstSelector() override;
};
} // namespace
\end{cpp}

\item
有了这个定义，就可以实现createPassConfig工厂方法:

\begin{cpp}
TargetPassConfig *M88kTargetMachine::createPassConfig(
        PassManagerBase &PM) {
    return new M88kPassConfig(*this, PM);
}
\end{cpp}

\item
最后，必须在addInstSelector()方法中将指令选择类添加到通道流水线中。返回值false表示添加了一个将LLVM IR转换为机器指令的通道:

\begin{cpp}
bool M88kPassConfig::addInstSelector() {
        addPass(createM88kISelDag(getTM<M88kTargetMachine>(),
                                  getOptLevel()));
    return false;
}
\end{cpp}
\end{enumerate}

完成实现是一段漫长的旅程!现在，我们可以构建llc工具了，可以运行一个示例，将下面的简单IR保存在and.ll文件中:

\begin{shell}
define i32 @f1(i32 %a, i32 %b) {
    %res = and i32 %a, %b
    ret i32 %res
}
\end{shell}

现在，可以运行llc并验证生成的程序集看起来是合理的:

\begin{shell}
$ llc -mtriple m88k-openbsd < and.ll
        .text
        .file "<stdin>"
        .globl f1                         | -- Begin function f1
        .align 2
        .type f1,@function
f1:                                       | @f1
| %bb.0:
        and %r2, %r2, %r3
        jmp %r1
.Lfunc_end0:
        .size   f1, .Lfunc_end0-f1
                                          | -- End function
        .section        ".note.GNU-stack","",@progbits
\end{shell}

要针对m88k目标进行编译，必须在命令行或IR文件中指定这个三元组，如本例中所示。

了解全局指令选择之前，先享受一下成功吧！



